package main

import (
	"bufio"
	"bytes"
	"fmt"
	"io"
	"os"
	"os/exec"
	"regexp"
	"strconv"
	"strings"
	"time"
)

type calTPS struct {
	tps_rule   string         //  tps_rule for regex
	re         *regexp.Regexp // log file regex
	timeLayout string         // timeLayout for datetime format
	loc        *time.Location // location for datetime format
	TPSOutput  *os.File       // fabric TPS output
}

func runCmd(cmdStr string) string {
	list := strings.Split(cmdStr, " ")
	cmd := exec.Command(list[0], list[1:]...)
	var out bytes.Buffer
	var stderr bytes.Buffer
	cmd.Stdout = &out
	cmd.Stderr = &stderr
	err := cmd.Run()
	if err != nil {
		return stderr.String()
	} else {
		return out.String()
	}
}

func getFabricPeerLogPath() (string, error) {
	peer_log := runCmd("docker ps")
	peer_log_lines := strings.Split(peer_log, "\n")
	for _, peer_log_line := range peer_log_lines {
		if strings.Contains(peer_log_line, "fabric-peer") {
			peer_log_line_contents := strings.Split(peer_log_line, " ")
			peer_container_name := peer_log_line_contents[len(peer_log_line_contents)-1]
			fmt.Println(peer_container_name)
			peer_log := runCmd("docker inspect --format='{{.LogPath}}' " + peer_container_name)
			peer_log = strings.ReplaceAll(peer_log, "'", "")
			peer_log = strings.ReplaceAll(peer_log, "\n", "")
			// fmt.Println("-" + peer_log + "-")
			fmt.Println(runCmd("sudo chmod 0666 " + peer_log))
			return peer_log, nil
		}
	}
	return "", fmt.Errorf("No peer container(s) running")
}

func (t *calTPS) getBlockNumTimestamp(line []byte) (int, int, int64, error) {
	strfid := string(line)
	ret := t.re.FindStringSubmatch(strfid)
	if ret == nil {
		return 0, 0, 0, fmt.Errorf("")
	}
	blockNum, err := strconv.Atoi(ret[1])
	if err != nil {
		return 0, 0, 0, err
	}
	blockSize, err := strconv.Atoi(ret[2])
	if err != nil {
		return 0, 0, 0, err
	}
	dateTime := ret[3] + " " + ret[4]
	tmp, _ := time.ParseInLocation(t.timeLayout, dateTime, t.loc)
	timestamp := tmp.UnixNano()
	// fmt.Println(timestamp)
	return blockNum, blockSize, timestamp, nil
}

func (t *calTPS) calCurrentTps(blockNum int, blockSize int, timeDuration int64) error {
	cur_tps := int64(blockSize*1e9) / timeDuration
	content := "TPS while committing block [" + strconv.Itoa(blockNum) + "]: " + strconv.FormatInt(cur_tps, 10) + "\n"
	_, err := t.TPSOutput.Write([]byte(content))
	if err != nil {
		return err
	}
	return nil
}

func main() {
	// get fabric peer log file
	peerLogPath, err := getFabricPeerLogPath()
	if err != nil {
		panic(err)
	}
	fileObj, err := os.Open(peerLogPath)
	if err != nil {
		panic(err)
	}
	defer fileObj.Close()

	stepLength := 20 // block count when cal tps

	calTps := calTPS{
		tps_rule:   `.*Committed block \[([0-9]+)\] with ([0-9]+).*"time":"(.*)T(.*)Z.*`,
		timeLayout: "2006-01-02 15:04:05.999999999",
	}
	calTps.re = regexp.MustCompile(calTps.tps_rule)
	calTps.loc, _ = time.LoadLocation("Local")
	calTps.TPSOutput, err = os.OpenFile("./fabricCurrentTPS.log", os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0666)
	if err != nil {
		panic(err)
	}
	defer calTps.TPSOutput.Close()

	fread := bufio.NewReader(fileObj)

	var timestamp1 int64
	for { // get first block timestamp
		line, _, err := fread.ReadLine()
		if err == io.EOF {
			return
		}
		_, _, timestamp1, err = calTps.getBlockNumTimestamp(line)
		if err == nil {
			break
		}
	}
	blockSizeCount := 0
	for {
		line, _, err := fread.ReadLine()
		if err == io.EOF {
			break
		}
		blockNum, blockSize, timestamp2, err := calTps.getBlockNumTimestamp(line)
		if err != nil {
			continue
		}
		blockSizeCount += blockSize
		if blockNum%stepLength == 0 {
			err = calTps.calCurrentTps(blockNum, blockSizeCount, timestamp2-timestamp1)
			if err != nil {
				panic(err)
			}
			blockSizeCount = 0
			timestamp1 = timestamp2
		}
	}
}
